/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.vm.stack;

import axil.api.AxilObject;
import axil.api.error.Frame;
import axil.api.extend.Parameters;
import axil.api.extend.Source;
import axil.engine.Repository;

import java.util.ArrayList;
import java.util.List;


/**
 * An Axil stack frame object. This has been optimized for the typical execution
 * scenarios. So, it may look a bit odd, but the oddness has significant
 * performance implications.
 */
public abstract class StackFrame implements Frame, Parameters {
	public static final int unknown = -1;
	public static final StackFrame root = new SF0();

	private final StackFrame parent;
	private final Source source;


	protected StackFrame() {
		parent = null;
		source = null;
	}


	protected StackFrame(StackFrame parent, Source source) {
		this.parent = parent;
		this.source = source;
	}


	public static StackFrame create(Parameters parent, Source source, int[] args) {
		StackFrame p = (StackFrame)parent;
		switch (args.length) {
		case 0:
			return new SF0(p, source);
		case 1:
			return new SF1(p, source, args[0]);
		case 2:
			return new SF2(p, source, args[0], args[1]);
		default:
			return new SFN(p, source, args);
		}
	}


	public static StackFrame create(Parameters parent, Source source,
	                                int[] args, AxilObject[] actual) {
		final int size = args.length;
		if (size != actual.length) {
			return new SFX((StackFrame)parent, source, args, actual);
		}
		return create(parent, source, args);
	}


	/**
	 * Get a description of the source where the error occurred. For functions
	 * and other forms from a module, the string will contain the module first,
	 * then the name of the function, separated by a slash character. For an
	 * ad hoc script compiled via the Axil.compile() method, the value is simply
	 * "script".
	 *
	 * @return
	 * 	Returns the source of the error.
	 */
	public String origin() {
		return Repository.instance().source(source.source());
	}


	/**
	 * Get the line in the source where the exception occurred. If the error
	 * did not occur in an area where source information is available, then
	 * zero is returned.
	 *
	 * @return
	 * 	Returns the line in the source. Lines are numbered starting with 1.
	 */
	public int line() {
		return source.line();
	}


	/**
	 * Get a description of this stack frame. This string is intended as a
	 * friendly aid to the user in diagnosing the source of the problem.The
	 * string returned is formatted and localized in a manner suitable for
	 * display to the user.  Localization is performed using the locale that
	 * existed in the context when the script was executed. The string returned
	 * does not contain the line number.
	 *
	 * @return
	 * 	Returns a string that is not null or empty.
	 */
	public String describe() {
		// TODO: Put actual values on the stack at this point
		return origin() + ", line " + line();
	}


	public abstract void a(AxilObject v);
	public abstract void b(AxilObject v);
	public abstract void c(AxilObject v);


	/**
	 * Set the value of the specified stack index variable to the given value.
	 * The value given may be nil but never null. This only sets the value into
	 * the existing frame, never upstream into parent frames.
	 */
	public abstract void at(int index, AxilObject value);

	/**
	 * Get the value with the given unique ID, or return a null if no such
	 * object can be found in this frame. This does NOT look into the parent
	 * frame.
	 */
	protected abstract AxilObject lookup(int id);


	/**
	 * Get the value from the stack keyed by the given unique ID. If the current
	 * stack frame does not contain the value, then the chain of parent frames
	 * is traversed until the root is reached. Reaching the root without finding
	 * the value results in an exception.
	 */
	public AxilObject id(int id) {
		AxilObject v = lookup(id);
		if (v == null) {
			v = parent.id(id);
		}
		return v;
	}


	/**
	 * Recursively rebuild this stack, in reverse order. That puts it in the
	 * order in which the user expects to see it.
	 */
	private List<Frame> frames(List<Frame> list) {
		if (parent != root) {
			parent.frames(list);
		}
		list.add(this);
		return list;
	}


	/**
	 * The stack trace for the script. The stack frames are listed in order from
	 * the starting point to the point at which execution halted. Since these
	 * frames are in the reverse order, we reverse them.
	 *
	 * @return
	 * 	Returns the set of stack frames. The list returned may be empty if the
	 * 	execution issue occurred outside of the script source code.
	 */
	public List<Frame> stack() {
		return frames(new ArrayList<Frame>());
	}
}
